#include "sys.h"

#include <netinet/in.h>

namespace sfc::net::sys {

static auto operator~(Domain self) -> int {
  switch (self) {
    case Domain::V4:
      return AF_INET;
    case Domain::V6:
      return AF_INET6;
    case Domain::Unix:
      return AF_UNIX;
    default:
      return AF_UNSPEC;
  }
};

static auto operator~(Type self) -> int {
  switch (self) {
    case Type::Stream:
      return SOCK_STREAM;
    case Type::Dgram:
      return SOCK_DGRAM;
    case Type::Raw:
      return SOCK_RAW;
    default:
      return SOCK_RAW;
  }
};

struct Socket::Addr : sockaddr {
  auto len() const -> u32 {
    switch (this->sa_family) {
      case AF_INET:
        return sizeof(sockaddr_in);
      case AF_INET6:
        return sizeof(sockaddr_in6);
      default:
        return sizeof(sockaddr);
    }
  }
};

Socket::Socket() noexcept : Base{} {}

Socket::Socket(io::File&& f) noexcept : Base{mem::move(f)} {}

Socket::Socket(Domain domain, Type type) : Base{} {
  this->_fd = ::socket(~domain, ~type, 0);
  if (this->_fd == -1) {
    throw Error::last_os_error();
  }
}

void Socket::listen(int backlog) {
  const auto ret = ::listen(_fd, backlog);
  if (ret == -1) {
    throw Error::last_os_error();
  }
}

auto Socket::accept() -> Socket {
  const auto fd = ::accept(_fd, nullptr, 0);
  if (fd == -1) {
    throw Error::last_os_error();
  }
  return Socket{io::File{fd}};
}

void Socket::bind(const Addr& addr) {
  const auto res = ::bind(_fd, &addr, addr.len());
  if (res == -1) {
    throw Error::last_os_error();
  }
}

void Socket::connect(const Addr& addr) {
  const auto res = ::connect(_fd, &addr, addr.len());
  if (res == -1) {
    throw Error::last_os_error();
  }
}

}  // namespace sfc::net::sys
